/*
 * Copyright (c) 2010-2011, okazoh_tk. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  - Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *  - Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *  - Neither the name of the copyright holder nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "alkes/graph/Layer.h"
#include "alkes/graph/Visual.h"
#include "alkes/graph/Composite.h"
#include "alkes/gfx/IGraphics3DDevice.h"
#include "alkes/graph/Updater.h"
#include "alkes/graph/Renderer.h"

namespace alkes {

Layer::Layer()
: opacity_(100)
, layer_blend_mode_(AL_LAYER_BLEND_NORMAL)
{
    setDrawable(true);

    // Initialize with target size.
//    setRect(target->getTargetRect());
}

Layer::~Layer()
{
    removeAllScene();
}

Scene* Layer::registerScene(Scene* scene)
{
    if (scene)
    {
        if (scene_list_.push(scene))
        {
            addChild(scene->getRootNode());
        }
    }

    return scene;
}

void Layer::removeScene(Scene* scene)
{
    removeChild(scene->getRootNode());

    if (scene_list_.remove(scene))
    {
        delete scene;
    }
}

void Layer::removeAllScene()
{
    Scene* scene;

    while (0 != (scene = static_cast<Scene*>(scene_list_.shift())))
    {
        removeChild(scene->getRootNode());
        delete scene;
    }
}

void Layer::setOpacity(int16_t opacity)
{
    setProperty(opacity_, opacity);
}

int16_t Layer::getOpacity() const
{
    return opacity_;
}

void Layer::setLayerBlendMode(LayerBlendMode mode)
{
    setProperty(layer_blend_mode_, mode);
}

LayerBlendMode Layer::getLayerBlendMode() const
{
    return layer_blend_mode_;
}

bool Layer::update(UpdateContext& uc)
{
    return updateChildren(uc);
}

bool Layer::updateChildren(UpdateContext& uc)
{
    Updater updater;
    updater.setUpdateContext(&uc);

    updater.traverse(this);

    return uc.hasDirtyRect();
}

void Layer::render(GraphicsContext& gc, const Rect& render_rect)
{
    // Set default scissor rect
    gc.pushGlobalScissor(render_rect);

    renderChildren(gc);

    // restore
    gc.popScissorRect();
}

void Layer::renderChildren(GraphicsContext& gc)
{
    Renderer renderer;

    renderer.setGraphicsContext(&gc);

    renderer.traverse(this);
}

bool Layer::onPreRender(GraphicsContext& gc)
{
    IGraphics2DDevice* device = gc.getGraphics2DDevice();
    if (!device)
        return false; // no target

    bool need_to_render = Clippable::onPreRender(gc);

    if (need_to_render)
    {
        opacity_backup_ = gc.getOpacity();

        gc.setOpacity(opacity_);

        setAlphaBlendMode(device);
    }

    return need_to_render;
}

void Layer::onPostRender(GraphicsContext& gc)
{
    Clippable::onPostRender(gc);

    gc.setOpacity(opacity_backup_);
}

static struct {
    AlphaBlendMode src_mode;
    AlphaBlendMode dst_mode;
} s_blend_table[] = {
    {AL_BLEND_ONE,          AL_BLEND_ZERO},         // AL_LAYER_BLEND_OPACITY
    {AL_BLEND_SRCALPHA,     AL_BLEND_INVSRCALPHA},  // AL_LAYER_BLEND_NORMAL
    {AL_BLEND_DSTCOLOR,     AL_BLEND_ZERO},         // AL_LAYER_BLEND_MULTIPLY
    {AL_BLEND_ONE,          AL_BLEND_ONE},          // AL_LAYER_BLEND_SCREEN
};

void Layer::setAlphaBlendMode(IGraphics2DDevice* device)
{
    if (layer_blend_mode_)
    {
        device->setState(AL_GS_ALPHA_BLEND, true);
        device->setAlphaBlendMode(s_blend_table[layer_blend_mode_].src_mode, s_blend_table[layer_blend_mode_].dst_mode);
    }
    else
    {
        device->setState(AL_GS_ALPHA_BLEND, false);
    }
}

}
